package com.thesis.manage.common.mongodb.impl;


import com.thesis.manage.common.constant.YesOrNoEnum;
import com.thesis.manage.common.entity.AbstractMongoEntity;
import com.thesis.manage.common.exception.CommonException;
import com.thesis.manage.common.mongodb.MongoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @author php
 * @date 2017/11/27
 * mongodb公共服务类
 */
public abstract class AbstractMongoServiceImpl<T extends AbstractMongoEntity> implements MongoService<T> {

    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMongoServiceImpl.class);
    private Map<String, Field> fields = new HashMap<>();
    @Resource
    private MongoTemplate mongoTemplate;

    protected AbstractMongoServiceImpl() {
        getFields();
    }

    /**
     * 获取实体的所有类型（包含父类）
     *
     * @return class
     */
    protected abstract Class<T> getEntityClass();

    private void getFields() {
        List<Class> classes = new ArrayList<>();
        Class entityClass = getEntityClass();
        while (entityClass != AbstractMongoEntity.class) {
            classes.add(entityClass);
            entityClass = entityClass.getSuperclass();
        }
        classes.add(AbstractMongoEntity.class);
        for (Class clazz : classes) {
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field descriptor : declaredFields) {
                fields.put(descriptor.getName(), descriptor);
            }
        }
        fields.remove("openId");
    }

    @Override
    public List<T> findAll() {
        List<T> all;
        try {
            Query query = new Query();
            query.addCriteria(Criteria.where("delStatus").is(YesOrNoEnum.NO.getValue()));
            all = this.mongoTemplate.find(query, getEntityClass());
        } catch (RuntimeException e) {
            LOGGER.error("mongodb.findAll.failed", e);
            throw new CommonException("mongodb.findAll.failed", "mongodb查询所有失败");
        }
        return all;
    }

    @Override
    public T findById(Long id) {
        T t;
        try {
            Query query = new Query();
            query.addCriteria(Criteria.where("id").is(id));
            query.addCriteria(Criteria.where("delStatus").is(YesOrNoEnum.NO.getValue()));
            t = this.mongoTemplate.findOne(query, getEntityClass());
        } catch (RuntimeException e) {
            LOGGER.error("mongodb.findById.failed", e);
            throw new CommonException("mongodb.findById.failed", "mongodb单条查询失败");
        }
        return t;
    }

    @Override
    public T findByOne(T t) {
        T entity = null;
        Query query = buildQuery(t, null);
        List<T> list = this.mongoTemplate.find(query, getEntityClass());
        if (!list.isEmpty()) {
            entity = list.get(0);
        }
        return entity;
    }

    @Override
    public Long queryCount(T t) {
        Query query = this.buildQuery(t, null);
        return this.mongoTemplate.count(query, getEntityClass());
    }

    @Override
    public Long update(T t) {
        if (null == t || null == t.getId()) {
            throw new CommonException("id不能为空");
        }
        Query query = new Query();
        Criteria criteria = Criteria.where("id").is(t.getId());
        query.addCriteria(criteria);
        t.setEndTime(new Date());
        Update update = setUpdate(t);
        T modify = this.mongoTemplate.findAndModify(query, update, getEntityClass());
        return modify.getId();
    }

    @Override
    public void insert(T t) {
        if (null == t) {
            throw new CommonException("保存对象失败，对象未示例化");
        }
        if (null != t.getId()) {
            this.update(t);
            return;
        }
        t.setStartTime(new Date());
        t.setEndTime(t.getStartTime());
        t.setDelStatus(YesOrNoEnum.NO.getValue());
        this.mongoTemplate.insert(t);
    }

    @Override
    public void insertAll(List<T> ts) {
        this.mongoTemplate.insertAll(ts);
    }

    /**
     * 真实删除
     *
     * @param id id
     */
    @Override
    public void remove(Long id) {
        Query query = new Query();
        query.addCriteria(Criteria.where("id").is(id));
        this.mongoTemplate.remove(query, getEntityClass());
    }

    @Override
    public Page<T> findByPage(T t, Pageable pageable, Sort sort) {
        PageRequest pageRequest;
        if (pageable == null) {
            throw new CommonException("pageable.is.null", "pageable不能为空");
        }
        if (sort != null) {
            pageRequest = new PageRequest(pageable.getPageNumber(), pageable.getPageSize(), sort);
        } else {
            pageRequest = new PageRequest(pageable.getPageNumber(), pageable.getPageSize());
        }
        Query query = this.buildQuery(t, pageRequest);
        Long count = this.queryCount(t);
        List<T> list = this.mongoTemplate.find(query, getEntityClass());
        PageRequest request = new PageRequest(pageable.getPageNumber() - 1, pageable.getPageSize());
        return new PageImpl<>(list, request, count);
    }

    @Override
    public void deleteById(Long id) {
        Query query = new Query();
        query.addCriteria(Criteria.where("id").is(id));
        query.addCriteria(Criteria.where("delStatus").is(YesOrNoEnum.NO.getValue()));
        T entity;
        try {
            entity = getEntityClass().newInstance();
        } catch (Exception e) {
            throw new CommonException("删除mongodb数据实败", e.toString());
        }
        entity.setDelStatus(YesOrNoEnum.YES.getValue());
        entity.setEndTime(new Date());
        Update update = setUpdate(entity);
        this.mongoTemplate.findAndModify(query, update, getEntityClass());
    }

    private Update setUpdate(T t) {
        Update update = new Update();
        for (Field field : fields.values()) {
            field.setAccessible(true);
            if (!"id".equals(field.getName()) && !"openId".equals(field.getName()) && !"startTime".equals(field.getName())) {
                Object o;
                try {
                    o = field.get(t);
                } catch (IllegalAccessException e) {
                    throw new CommonException(e);
                }
                if (null != o) {
                    update.set(field.getName(), o);
                }
            }
        }
        return update;
    }

    private Query buildQuery(T t, Pageable pageable) {
        t.setDelStatus(YesOrNoEnum.NO.getValue());
        Query query = new Query();
        for (Field field : fields.values()) {
            field.setAccessible(true);
            Object o;
            try {
                o = field.get(t);
            } catch (IllegalAccessException e) {
                throw new CommonException(e);
            }
            if (null != o) {
                query.addCriteria(Criteria.where(field.getName()).is(o));
            }
        }
        if (null != pageable) {
            PageRequest pageRequest;
            if (pageable.getSort() == null) {
                pageRequest = new PageRequest(pageable.getPageNumber() - 1, pageable.getPageSize(), new Sort(Sort.Direction.ASC, "startTime"));
                query.with(pageRequest);
            } else {
                pageRequest = new PageRequest(pageable.getPageNumber() - 1, pageable.getPageSize(), pageable.getSort());
                query.with(pageRequest);
            }
        }
        return query;
    }

    @Override
    public List<T> find(T t) {
        Query query = this.buildQuery(t, null);
        query.with(new Sort(Sort.Direction.ASC, "startTime"));
        return this.mongoTemplate.find(query, getEntityClass());
    }
}
